package vip.xumy.admin.bmap.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import vip.xumy.admin.bmap.pojo.DownWorker;
import vip.xumy.admin.bmap.pojo.Point;
import vip.xumy.admin.bmap.pojo.WorkContext;
import vip.xumy.admin.bmap.service.LoadWorkService;
import vip.xumy.core.utils.DateUtil;

/**
 * @author:mengyxu
 * @date:2020年4月10日
 */

@Log4j2
@Component
@EnableScheduling
public class DownloadWorker {
	private static final LinkedBlockingQueue<Point> queue = new LinkedBlockingQueue<>(1000);
	private static final ExecutorService executor = Executors.newFixedThreadPool(100);
	private static final LinkedBlockingQueue<WorkContext> works = new LinkedBlockingQueue<>(500);
	private static final String urlFromat = "http://{0}.bdimg.com/tile/?qt=vtile&x={1}&y={2}&z={3}&styles=pl";
	private static final String[] MAP_HOSTS = { "maponline0", "maponline1", "maponline2", "maponline3" };

	@Value("${image.bmap.maptile}")
	private String path;
	@Value("${bmap.download.size}")
	private int size;

	@Autowired
	public void setWorkService(LoadWorkService workService) {
		this.workService = workService;
		this.init();
	}

	private LoadWorkService workService;

	@Getter
	private WorkContext context;
	private Lock lockS = new ReentrantLock();
	private Lock lockF = new ReentrantLock();
	private Lock lockE = new ReentrantLock();

	public void init() {
		DownWorker working = workService.working();
		if (working != null) {
			working.setStatus(2);
			working.setResult(2);
			workService.update(working);
		}
		List<DownWorker> waitList = workService.waitList();
		if (waitList == null || waitList.isEmpty()) {
			return;
		}
		for (int i = waitList.size(); i > 0; i--) {
			DownWorker worker = waitList.get(i - 1);
			WorkContext context = new WorkContext();
			BeanUtils.copyProperties(worker, context);
			try {
				works.put(context);
			} catch (InterruptedException e) {
				context.setStatus(2);
				context.setResult(3);
				workService.update(context);
			}
		}
	}

	public void addTask(WorkContext context) throws InterruptedException {
		works.put(context);
	}

	@Scheduled(cron = "${bmap.download.work.cron}")
	public void createWorker() throws InterruptedException {
		if (context == null) {
			if (!works.isEmpty()) {
				context = works.take();
				;
			} else {
				return;
			}
		} else if (context.isDone()) {
			context.setEndTime(DateUtil.format(new Date(), DateUtil.FORMAT19_LINE_YYYYMMDDHHMMSS));
			context.setStatus(2);
			context.setResult(1);
			workService.update(context);
			if (!works.isEmpty()) {
				context = works.take();
				;
			} else {
				context = null;
				return;
			}
		}
		addCaller();
		addWorker(size - context.getSize());
		log.debug(context.status());
	}

	private void addCaller() {
		if (context.started()) {
			return;
		}
		Future<?> submit = executor.submit(() -> {
			context.setStartTime(DateUtil.format(new Date(), DateUtil.FORMAT19_LINE_YYYYMMDDHHMMSS));
			context.setStart(System.currentTimeMillis());
			context.setStatus(1);
			workService.update(context);
			log.info(context.getCityName() + "-submit线程开始");
			Point leftBottom = context.getLeftBottom();
			Point rightTop = context.getRightTop();
			long start = System.currentTimeMillis();
			for (int z = context.getMinZom(); z <= context.getMaxZom(); z++) {
				leftBottom.setZ(z);
				rightTop.setZ(z);
				BMapTool.callXY(leftBottom);
				BMapTool.callXY(rightTop);
				log.debug(MessageFormat.format("Z:{0},X:{1}-{2},Y:{3}-{4}", z, leftBottom.getX(), rightTop.getX(),
						leftBottom.getY(), rightTop.getY()));
				for (int x = leftBottom.getX(); x <= rightTop.getX(); x++) {
					for (int y = leftBottom.getY(); y <= rightTop.getY(); y++) {
						Point p = new Point(x, y, z);
						try {
							queue.put(p);
						} catch (InterruptedException e) {
							fail(p);
						}
					}
				}
			}
			long time = (System.currentTimeMillis() - start) / 1000;
			log.info(context.getCityName() + "-submit线程结束,耗时" + time + "秒,速度约"
					+ (context.getTotal() - context.getExist() - 1000) / time + "个/秒");
		});
		context.setSubmit(submit);
	}

	private void addWorker(int size) {
		for (int i = 0; i < size; i++) {
			Future<?> future = executor.submit(() -> {
				while (!queue.isEmpty()) {
					try {
						Point point = queue.take();
						if (!downLoad(point)) {
							fail(point);
						}
					} catch (InterruptedException e) {
						log.error("download failed", e);
					}
				}
				System.out.println(Thread.currentThread().getName() + "下载线程结束");
			});
			context.addFuture(future);
		}
	}

	private boolean downLoad(Point point) {
		int z = point.getZ();
		int x = point.getX();
		int y = point.getY();
		File file = new File(path + "/" + z + "/" + x + "/" + y + ".png");

		if (file.exists()) {
			exist();
			return true;
		}
		new File(file.getParent()).mkdirs();
		int idx = Math.abs(x + y) % MAP_HOSTS.length;
		String urlStr = MessageFormat.format(urlFromat, MAP_HOSTS[idx], x + "", y + "", z);
		URL url;
		try {
			url = new URL(urlStr);
		} catch (MalformedURLException e) {
			log.error("download failed:" + e.getMessage());
			return false;
		}
		try (InputStream is = url.openConnection().getInputStream();
				FileOutputStream os = new FileOutputStream(file);) {
			byte[] buf = new byte[1024 * 20];
			int bytesRead;
			while ((bytesRead = is.read(buf)) > 0) {
				os.write(buf, 0, bytesRead);
			}
			os.flush();
			success();
			return true;
		} catch (ConnectException | FileNotFoundException e) {
			log.error(e.getMessage());
			file.delete();
			return false;
		} catch (Exception e) {
			log.error("download failed", e);
			file.delete();
			return false;
		}
	}

	private void success() {
		lockS.lock();
		context.success();
		lockS.unlock();
	}

	private void fail(Point p) {
		lockF.lock();
		context.fail();
		lockF.unlock();
	}

	private void exist() {
		lockE.lock();
		context.exist();
		lockE.unlock();
	}

}
